package net.xiaosaguo.mymall.order.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * description: Rabbit 自定义配置
 *
 * @author xiaosaguo
 * @date 2020/07/09
 */
@Configuration
public class RabbitConfig {

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 定制 RabbitTemplate
     * 1. 消息正确抵达 Broker 回调，服务收到消息就回调
     * -    1. spring.rabbitmq.publisher-confirms=true
     * -       spring.rabbitmq.publisher-confirm-type=correlated
     * -    2. 设置抵达 Broker 确认回调
     * 2. 消息正确抵达队列回调
     * -    1. # 开启发送端确认消息抵达 Queue
     * -       spring.rabbitmq.publisher-returns=true
     * -       # 只要消息抵达队列，以异步方式，优先回调 return-confirm
     * -       spring.rabbitmq.template.mandatory=true
     * 3. 消费端确认（保证每个消息被正确消费，此时才可以 Broker 删除这个消息）
     * -    1. 默认是自动确认的，只要消息接收到，客户端会自动确认，服务端就会移除这个消息
     * -        问题：
     * -            收到很多消息，自动回复给服务器 ack，只有一个消息处理成功，宕机了：发生消息丢失
     * -        解决问题：手动确认模式：
     * -            只要客户端没有明确告诉 Broker，消息被消费（ack），消息就一直是 unacked 状态，
     * -            即使 Consumer 宕机，消息也不会丢失，会重新变为 ready 状态，下一次有新的 Consumer 连接进来就发给它
     * -    2. 手动 ack
     * -        1. 消费：业务成功
     * -            channel.basicAck(deliveryTag, false);
     * -        2. 拒消：业务失败
     * -            channel.basicNack(deliveryTag, false, true);
     * -            channel.basicReject(deliveryTag, true);
     */
    @PostConstruct
    public void initRabbitTemplate() {
        // 设置确认回调
        // 消息正确抵达 Broker 确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 只要消息抵达 Broker ，就 ack = true
             *
             * @author xiaosaguo
             * @date 2020/07/10 10:50
             * @param correlationData 当前消息的唯一关联数据（消息的唯一ID）
             * @param ack 消息是否成功收到
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("\n>=confirm..." +
                        "\n=correlationData:" + correlationData +
                        "\n=ack:" + ack +
                        "\n<=cause:" + cause
                );
            }
        });

        // 消息正确抵达 Queue 确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 触发时机：只要消息没有投递给指定的队列，就触发这个失败回调
             * 使用场景：将所有消息存储到数据库中，收到回调后就将消息标记为相应的状态，定时扫描这个表，将没有成功投递的重新投递
             *
             * @author xiaosaguo
             * @date 2020/07/10 11:13
             * @param message 投递失败的消息的详细信息
             * @param replyCode 回复的状态吗
             * @param replyText 回复的文本内容
             * @param exchange 消息是发给哪个交换机的
             * @param routingKey 消息的路由键
             *
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("\n>===failure-return-confirm..." +
                        "\n===message:" + message +
                        "\n===replyCode:" + replyCode +
                        "\n===replyText:" + replyText +
                        "\n===exchange:" + exchange +
                        "\n<===routingKey:" + routingKey);
            }
        });
    }
}
